-
Notifications
You must be signed in to change notification settings - Fork 105
Full review #138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Full review #138
Conversation
Add artifact id
Add TaskStore and TaskManager
Closes #54
Implement the ability to send a streaming message request using A2AClient#sendStreamingMessage
Add a RequestClass
Add the event classes from the Python implementation
Share the Executor used for async operations
Refactor A2A.getAgentCard() to use AgentCardProducer. Python impl uses different exception types from what we were.
Add AgentCardProducer
…erver, and update dependencies accordingly (#124) This PR splits the examples into two different modules, one for client and one for server and updates the instructions accordingly. It also cleans up some dependencies and removes unnecessary ones.
…a2a-java-sdk-server-common module (#129) This PR splits out server related code from the `a2a-java-sdk-core` module into a new `a2a-java-sdk-server-common` module that is used by the quarkus and jakarta server modules. This reduces the dependencies required by the `a2a-java-sdk-core` module.
I found some unused imports in the project. This PR removes them to keep the code clean. Signed-off-by: Sun Yuhan <[email protected]> Co-authored-by: Sun Yuhan <[email protected]>
This PR includes the following changes: 1. Added a unit test class for the `RequestContext` class, and all unit tests have been verified to pass. This is based on the unit test class from the `a2a` Python sdk: `tests/server/agent_execution/test_context.py`, with a few tests that are not applicable to the Java version removed. 2. Based on the unit test class, an issue in the `RequestContext` class was identified and fixed: the `relatedTasks` setting in the constructor was not working properly. Fixes #60 --------- Signed-off-by: Sun Yuhan <[email protected]> Co-authored-by: Sun Yuhan <[email protected]>
# Description for issue task #20 - [x] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests pass - [ ] Appropriate READMEs were updated (if necessary) Fixes #<issue_number_goes_here> 🦕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @rajeshvelicheti, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request marks the initial release of the Java SDK for the Agent2Agent (A2A) Protocol. It establishes a comprehensive framework for building A2A-compliant applications in Java, encompassing both client and server functionalities. The changes include a modular project structure, detailed protocol specification, robust HTTP communication utilities, and clear documentation with practical examples to facilitate adoption and contribution.
Highlights
- Initial SDK Release: This pull request introduces the foundational Java SDK for the Agent2Agent (A2A) Protocol, establishing core client and server components.
- Comprehensive Protocol Implementation: The SDK includes detailed Java representations of A2A protocol elements such as Agent Cards, Messages, Tasks, Artifacts, and various data parts (text, file, data), along with robust JSON-RPC message handling.
- Full-Featured A2A Client: A complete A2A client is provided, supporting synchronous and streaming message sending, task management (retrieval, cancellation), and push notification configuration, leveraging a new HTTP client abstraction.
- Flexible A2A Server Implementations: The SDK offers common server-side components for agent execution and task management, with specific implementations for Jakarta REST and Quarkus Reactive Routes, enabling diverse deployment options.
- Extensive Documentation and Examples: New
README.md,CONTRIBUTING.md,LICENSE, andSECURITY.mdfiles are added, alongside 'Hello World' client and server examples, to guide users and contributors.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces several new files and modifies existing ones to implement the A2A (Agent2Agent) protocol in Java. It includes core functionalities, server implementations for Jakarta REST and Quarkus Reactive Routes, client functionalities, and example usages. The changes also cover documentation, licensing, security reporting, and contributing guidelines.
|
|
||
| @Override | ||
| public EventQueue tap(String taskId) { | ||
| synchronized (taskId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential issue with string interning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fixed in #146. Originally by syncing on this.queues, later by replacing with ConcurrentHashMap
|
|
||
| @ApplicationScoped | ||
| public class InMemoryQueueManager implements QueueManager { | ||
| private final Map<String, EventQueue> queues = Collections.synchronizedMap(new HashMap<>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may be ConcurrentHashMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #146
| public EventQueue tap(String taskId) { | ||
| synchronized (taskId) { | ||
| EventQueue queue = queues.get(taskId); | ||
| if (queue == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: return null over return queue for better readability primarily because there is a null comparision before return. Also, if queues is a ConcurrentHashMap, it might be
EventQueue queue = queues.get(taskId);
return queue != null ? queue.tap() : null;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #146
| private final PushNotifier pushNotifier; | ||
| private final Supplier<RequestContext.Builder> requestContextBuilder; | ||
|
|
||
| private final Map<String, CompletableFuture<Void>> runningAgents = Collections.synchronizedMap(new HashMap<>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConcurrentHashMap maybe since there is a lot of async handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #146
| .build(), | ||
| queue); | ||
|
|
||
| CompletableFuture<Void> cf = runningAgents.get(task.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can use async composition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not totally clear what you mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of something like this
runningAgents.getOrDefault(task.getId(), CompletableFuture.completedFuture(null)).cancel(true); but this is more of a nit for readability, might not have a large gain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or even
Optional.ofNullable(runningAgents.get(task.getId()))
.ifPresent(task -> task.cancel(true));
|
|
||
| abstract void signalQueuePollerStarted(); | ||
|
|
||
| public void enqueueEvent(Event event) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async via CompetableFuture may be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not totally clear again :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as dequeue, public CompletableFuture enqueueEvent(Event event) {..}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rajeshvelicheti Just wondering whether or not it makes sense to make enqueueEvent async and return a CompletableFuture. With this approach, it doesn't seem like we can guarantee the order in which events get added to the queue.
For example, if enqueueEvent(eventA) is called followed by enqueueEvent(eventB) and if the enqueueEvent method is async and uses something like this code snippet to handle adding the event, it doesn't seem like we can guarantee that eventA will be added to the queue before eventB:
return CompletableFuture.supplyAsync(() -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Unable to acquire the semaphore to enqueue the event", e);
}
queue.add(event);
// Call toString() since for errors we don't really want the full stacktrace
log.debug("Enqueued event {} {}", event instanceof Throwable ? event.toString() : event, this);
return null;
});
@rajeshvelicheti Please let us know if we are missing something or if you had something else in mind. Thanks!
|
|
||
| abstract EventQueue tap(); | ||
|
|
||
| public Event dequeueEvent(int waitMilliSeconds) throws EventQueueClosedException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async via CompletableFuture may be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Kabir. I thought, public CompletableFuture dequeueEvent(int waitMilliSeconds) throws EventQueueClosedException {...}
might help in this, given how the method is structured is being used.
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public abstract class EventQueue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the usage, AutoCloseable might help the consumers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #146
| @ApplicationScoped | ||
| public class InMemoryTaskStore implements TaskStore { | ||
|
|
||
| private final Map<String, Task> tasks = Collections.synchronizedMap(new HashMap<>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConcurrentHashMap maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #146
|
|
||
| private final EventQueue parent; | ||
| // TODO decide on a capacity (or more appropriate queue data structures) | ||
| private final BlockingQueue<Event> queue = new ArrayBlockingQueue<Event>(1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be unbounded to handle blocking better, a bounded semaphore to handle back pressure might be effective
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit wooly on the details, but I have replaced ArrayBlockingQueue with LinkedBlockingQueue. The latter has a constructor where you don't specify the capacity, so I took a guess at this being a better structure from your input. Then I added a fair semaphore to deal with the capacity.
Those changes are in #146.
If I have misunderstood something, please let me know.
|
@rajeshvelicheti I believe I have addressed most of your comments in #146. I wasn't sure what you meant where you mentioned CompletableFuture, so I have left those for now |
| .build(), | ||
| queue); | ||
|
|
||
| CompletableFuture<Void> cf = runningAgents.get(task.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of something like this
runningAgents.getOrDefault(task.getId(), CompletableFuture.completedFuture(null)).cancel(true); but this is more of a nit for readability, might not have a large gain.
| .build(), | ||
| queue); | ||
|
|
||
| CompletableFuture<Void> cf = runningAgents.get(task.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or even
Optional.ofNullable(runningAgents.get(task.getId()))
.ifPresent(task -> task.cancel(true));
| private void cleanupProducer(String taskId) { | ||
| // TODO the Python implementation waits for the producerRunnable | ||
| CompletableFuture<Void> cf = runningAgents.get(taskId); | ||
| if (cf != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if cf is null? would the queuemanager need to close?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, cf !=null could be avoided for readability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A cf means there is an active task being processed. In which case we remove (i.e. 'close') that task from the queuemanager. The queuemanager itself remains open.
That said, having revisited the code, I don't think the future can be null, so I will try removing the null check.
|
|
||
| @Override | ||
| public Flow.Publisher<StreamingEventKind> onMessageSendStream(MessageSendParams params) throws JSONRPCError { | ||
| TaskManager taskManager = new TaskManager( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
153-175 have same code, might be useful to convert to a common method. Nit
| } | ||
|
|
||
| } finally { | ||
| if (interrupted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
both if and else do the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a TODO with something to investigate from the Python implementation
|
|
||
| // if the taskId and contextId were specified, they must match the params | ||
| if (params != null) { | ||
| if (taskId != null && ! params.message().getTaskId().equals(taskId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: taskId.equals might be more helpful as there is a not null comparison on taskId already. Potential NPE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| } else { | ||
| checkOrGenerateTaskId(); | ||
| } | ||
| if (contextId != null && ! params.message().getContextId().equals(contextId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Potential NPE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| if (taskId != null && ! params.message().getTaskId().equals(taskId)) { | ||
| throw new InvalidParamsError("bad task id"); | ||
| } else { | ||
| checkOrGenerateTaskId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: heavy constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave this as-is for now. We need to create an id if not specified
| if (contextId != null && ! params.message().getContextId().equals(contextId)) { | ||
| throw new InvalidParamsError("bad context id"); | ||
| } else { | ||
| checkOrGenerateContextId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: heavy constructor
| private List<String> getTextParts(List<Part<?>> parts) { | ||
| return parts.stream() | ||
| .filter(part -> part.getKind() == Part.Kind.TEXT) | ||
| .map(part -> (TextPart) part) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TextPart - is this always expected? would the cast fail in any case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method seems too indicate we're explicitly looking for TextParts, and since we're filtering on Kind.TEXT the cast cannot fail
| } | ||
|
|
||
| public List<Task> getRelatedTasks() { | ||
| return relatedTasks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning a modifiable collection, is this okay? looking at the references might help to return an un-modifiable collection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've wrapped it in Collections.unmodifiableList() for now.
The main user seems to be the AgentExecutor, and I'm not totally clear if those should be able to add to this list. That said, there is a RequestContext.attachRelatedTask() that AgentExecutor implementors can call.
| return; | ||
| } catch (Exception e) { | ||
| // Continue polling until there is a final event | ||
| continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need the tube closed in this case as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I don't remember the exact details of if/how the Python implementation evolved in this area. But looking at the latest code there, it seems to do the equivalent of what you say.
I've updated this as per your suggestion.
| return ZeroPublisher.create(conf, tube -> { | ||
| boolean completed = false; | ||
| try { | ||
| while (true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
potential infinite loop, do we expect the tube to close all the time so there is no infinite loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should listen until the queue is closed (signalled via an EventQueueClosedException), an error is received, or a 'final' event is received.
I believe that is handled correctly?
# Description Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests pass - [x] Appropriate READMEs were updated (if necessary) Fixes #<issue_number_goes_here> 🦕
| */ | ||
| public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCardPath, Map<String, String> authHeaders) { | ||
| this.httpClient = httpClient; | ||
| if (!baseUrl.endsWith("/")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
java.net.URI over string, could help with the slashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182
| private final String url; | ||
| private final Map<String, String> authHeaders; | ||
|
|
||
| static String DEFAULT_AGENT_CARD_PATH = "/.well-known/agent.json"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be private final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182
|
|
||
| static String DEFAULT_AGENT_CARD_PATH = "/.well-known/agent.json"; | ||
|
|
||
| static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be private final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182
| } | ||
|
|
||
| private static boolean isStreamingRequest(String requestBody) { | ||
| return requestBody.contains(A2A.SEND_STREAMING_MESSAGE_METHOD) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Could be better handled by converting the body to a json object and inspect the appropriate element, method in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182
| requestBody.contains(A2A.SEND_TASK_RESUBSCRIPTION_METHOD); | ||
| } | ||
|
|
||
| private static boolean isNonStreamingRequest(String requestBody) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could not find any references to this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182
| // TODO There is the following line in the Python code I don't totally get | ||
| // asyncio.create_task(self._continue_consuming(event_stream)) | ||
| // I think it means the continueConsuming() call should be done in another thread | ||
| continueConsuming(all); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be useful to get this executed in a background thread. CompletableFuture.runAsync might be more helpful in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #182. Please let us know if that's not what you had in mind.
|
Hi @rajeshvelicheti, thanks for the feedback! I've addressed the latest comments in #182. |
# Description This PR incorporates the latest feedback from #138. - [x] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests pass - [x] Appropriate READMEs were updated (if necessary)
|
@rajeshvelicheti Since we've addressed all the comments here, I'll go ahead and close this. Feel free to re-open. |
# Description This PR incorporates the latest feedback from a2aproject#138. - [x] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests pass - [x] Appropriate READMEs were updated (if necessary)
Description
Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
CONTRIBUTINGGuide.fix:which represents bug fixes, and correlates to a SemVer patch.feat:represents a new feature, and correlates to a SemVer minor.feat!:, orfix!:,refactor!:, etc., which represent a breaking change (indicated by the!) and will result in a SemVer major.Fixes #<issue_number_goes_here> 🦕